#include "arp.h"

/* 
 * ARP: Address Resolution Protocol Implementation:
   1) Provides areq interface to be used by tour application
      for ping purpose.
   2) Implements ARP cache by using singly linked lists.
   3) Cleans up cache on client side errors.
   4) Uses ARP_ID - (0x2123 - 8483) for uniquley identify itself
      on the messages printed on the screen
 *
 */
	
void send_cli_reply(arp_list *anode, int *fd) {
	assert(anode);
	int confd = *fd; 
	int rc = 0;
	arp_hwaddr ahw;
	memset (&ahw, 0, sizeof(ahw));
	
	memcpy(ahw.hwaddr, anode->aentry.hwaddr.addr, ETH_ALEN);
       	ahw.halen = ETH_ALEN;
	ahw.hatype = 1;
	ahw.ifindex = anode->aentry.ifindex;

	// printf("Replying to the Cli\n");
	int nwrite = write(confd, (char *)&ahw, sizeof(ahw));
	if (nwrite < 0) {
		perror("ARP Module: Error wrtiting to a client socket: ");
		rc = -1;
		goto out;
	}
	if (nwrite == 0) {
		printf("ARP Module: Error writing to a client socket: EOF received\n");
		rc = -1;
		goto out;
	}
	assert(nwrite == sizeof(ahw));
out:	
	close(confd);
	/* Purge the entry on error */
	if (rc) {
		anode = del_arp_node(&arp_head, anode->aentry.ip, -2);
		assert(anode);
		free_arp_node(anode);
	}
	*fd = -1;
}

void send_arp_reply(arp_pkt *apkt_req, struct hwa_info *hw)
{
	// printf("Sending the arp reply\n");
	char buffer[ETH_FRAME_LEN + ETH_FCS_LEN];
	eth_pkt *epkt = (eth_pkt *)buffer;
	arp_pkt *apkt = &epkt->apkt;
	struct ethhdr *ehdr = &epkt->ehdr;

	memcpy(ehdr->h_dest, apkt_req->sender_mac.addr , ETH_ALEN);
	memcpy(ehdr->h_source, hw->if_haddr , ETH_ALEN);
	ehdr->h_proto = htons(ETH_P_ARP23);

	memcpy (apkt, apkt_req, sizeof(arp_pkt));
	apkt->op = htons(ARPOP_REPLY);
	
	memcpy(apkt->target_mac.addr, apkt_req->sender_mac.addr, ETH_ALEN);
	memcpy(apkt->target_ip, apkt_req->sender_ip, INET_ADDRSTRLEN);
	memcpy(apkt->sender_mac.addr, hw->if_haddr, ETH_ALEN);
	memcpy(apkt->sender_ip, hw->ip, INET_ADDRSTRLEN);
	
	send_frame(buffer, ETH_FRAME_LEN, hw->if_index);
	// printf("Sent\n");
}

void handle_cli_request(char *ipaddr, int *confd) 
{	
	/* Check if we have an entry in the cache */
	arp_list *anode = find_arp_node(arp_head, ipaddr);
	if (anode && anode->aentry.valid) {
		// printf("I know the dest mac\n");
		send_cli_reply(anode, confd);
		return;
	}
	
	/* Create an entry in the cache and broadcast
	 * the arp request
	 */
	if (!anode) {
		arp_entry aentry;
		memset(&aentry, 0, sizeof(aentry));
		memcpy(aentry.ip, ipaddr, INET_ADDRSTRLEN);
		aentry.confd = *confd;
		aentry.valid = false;
		create_arp_node(&arp_head, &aentry);
	}
	
	printf("ARP Module: Broadcasting the ARP Request for IP:%s\n", ipaddr);
	broadcast_arp_pkt(hweth0, ipaddr);
}

void process_request(arp_pkt *apkt, int ifidx)
{
	printf("ARP Module ID %hd: Received ARP_REQUEST for IP %s\n", apkt->arpid, apkt->target_ip);
	/* Check the cache and update/reply accodingly */
	struct hwa_info *tnode = find_hw_node(apkt->target_ip);
	arp_list *anode = find_arp_node(arp_head, apkt->sender_ip);
	if (tnode) {  		
		 // printf("I'm  the destination\n");
		/* I'm the destination, update the cache entry 
		 * for the sender and then send a reply. 
		 * update_arp_node takes care of creating one
		 * if already doesn't exist
		 */
		update_arp_node(&arp_head, anode, apkt, ifidx);
		send_arp_reply(apkt, tnode);
	}
	else if (anode) {
		/* If I know about the sender, I'm supposed to
		 * update its cache entry
		 */	
		// printf("I know about the sender, I'll update my cache\n");
		update_arp_node(&arp_head, anode, apkt, ifidx);
	}
}

void process_reply(arp_pkt *apkt, int ifidx)
{
	printf("ARP Module ID %d: Received ARP_REPLY from IP %s\n", apkt->arpid, apkt->sender_ip);
	/* update the cache and reply to client */
	arp_list *anode = find_arp_node(arp_head, apkt->sender_ip);
	assert(anode && !anode->aentry.valid);
	
	anode->aentry.valid = true;
	int confd = anode->aentry.confd;
	anode->aentry.confd = -1;
	update_arp_node(&arp_head, anode, apkt, ifidx);

	send_cli_reply(anode, &confd);
}	

void process_packet(arp_pkt *apkt, int ifidx)
{
	switch(ntohs(apkt->op)) {
		case ARPOP_REQUEST:
			process_request(apkt, ifidx);
			break;
		case ARPOP_REPLY:
			process_reply(apkt, ifidx);
			break;	
		default:
			assert(0);
	}
}

int main() {
	struct sockaddr_un arpaddr, cliaddr;
	char ethbuf[ETH_FRAME_LEN];
	fd_set rset;
	int sockfd, maxfd;
	const int on = 1;

	printf("Starting ARP Module ID %d\n", ARP_ID);

	/* populate the hw list for eth0 interfaces */
	populate_eth0_hw(true);

	/* Unix Domain Socket */
	maxfd = sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
	Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	
	memset(&arpaddr, 0, sizeof(arpaddr));

	arpaddr.sun_family = AF_LOCAL;
	strcpy(arpaddr.sun_path, ARP_SUNPATH);

	unlink(ARP_SUNPATH);
	Bind(sockfd, (SA *) &arpaddr, sizeof(arpaddr));
	Listen(sockfd, LISTENQ);
	
	/* PF Socket */	
	pfd = (int *) Malloc(ifcount * sizeof(int));
	pfa = (struct sockaddr_ll *) Malloc(ifcount * sizeof(struct sockaddr_ll));
	if_idx = (int *) Malloc(ifcount * sizeof(int));
	memset(pfa, 0,  sizeof(struct sockaddr_ll) * ifcount);

	sock_crbnd(pfd, pfa, &maxfd);
	int i, confd = -1;
	
	FD_ZERO(&rset);
	for(i = 0; i < ifcount; i++) 
		FD_SET(pfd[i], &rset);

	FD_SET(sockfd, &rset);
		
	while (1) {
		int i, rc;
		Select(maxfd + 1, &rset, NULL, NULL, NULL);
		
		for (i = 0; i < 1; i++) {
			if(FD_ISSET(pfd[i], &rset)) {
				/* read from this socket for arp reply/request from enthernet */
				rc = recvfrom(pfd[i], ethbuf, ETH_FRAME_LEN, 0, NULL, NULL);
				if(rc < 0)
					perror("Error receiving ");
				eth_pkt *epkt = (eth_pkt *)ethbuf;
				
				printf("ARP Module: Ethernet Frame received\n");
				printf("From Interface HW addr: ");
				print_mac(epkt->ehdr.h_source);
				printf(" To Interface HW addr: ");
				print_mac(epkt->ehdr.h_dest);
				printf("\n");

				process_packet(&epkt->apkt, if_idx[i]);
				confd = -1;
			}
		}

		/* getting a packet from client areq via the UDS */
		if(FD_ISSET(sockfd, &rset))
		{
			// printf("ARP Module: Connection request from client\n");
			confd = Accept(sockfd, (SA *) NULL, NULL);
			if (confd < 0) {
				perror("AREP MODULE: Error accepting client connection: ");
				continue;
			}
				
			char ipaddr_req[INET_ADDRSTRLEN];; 
			
			// printf("ARP Module: Connection accepted\n");
			// memset(&cliaddr, 0, sizeof(cliaddr));
			// int clilen = sizeof(cliaddr);
			// Recvfrom(confd, ipaddr_req, INET_ADDRSTRLEN, 0, (SA*) &cliaddr, &clilen);
			int nread = read(confd, ipaddr_req, INET_ADDRSTRLEN);
			if (nread <= 0) {
				perror("AREP MODULE: Error reading from client socket: ");
				continue;
			}

			// printf("ARP Module: IpAddr %s received\n", ipaddr_req);
			handle_cli_request(ipaddr_req, &confd);
		}
		if (confd != -1 && FD_ISSET(confd, &rset)) {
			printf("ARP Module: Error on client socket, purging the cache entry");
			/* Purge the entry on error */
			
			arp_list *anode = del_arp_node(&arp_head, NULL, confd);
			if (anode)
				free_arp_node(anode);
			close(confd);
			confd = -1;
		}
		FD_ZERO(&rset);
		for(i = 0; i < ifcount; i++) 
			FD_SET(pfd[i], &rset);

		FD_SET(sockfd, &rset);
		
		if (confd != -1) {
			FD_SET(confd, &rset);
			maxfd = max(maxfd, confd);
		}			
	}
}
